package net.javalib.flickr.search;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;

import net.javalib.flickr.search.flickr.Flickr;
import net.javalib.flickr.search.flickr.FlickrImage;
import net.javalib.flickr.search.util.CacheUtil;
import net.javalib.flickr.search.util.IOUtil;
import net.javalib.flickr.search.util.ProgressAwareInputStream;

import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;

import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;


/**
 * Local Service used to search for and download images from Flickr
 * 
 * App <-> Service communication based on work done by XXXX see YYYY
 * NOTE: Dig out references.
 * 
 * Basically the way the service and app work is via messages.
 * The application never binds to the service, instead it will send messages to the
 * service message handler which then performs some action depending on the 
 * message. The service performs these requests in background threads. These threads will
 * stop the service when they are done with the request and there are no
 * other pending messages.
 * 
 * This approach is perhaps a bit of over kill for this application's requirements, but 
 * since I had limited time to work on this app, I went with what I know. Binding to local 
 * services has always seemed problematic to me, since it tends to tie an activity's life-cycle
 * to the service.
 * 
 */
public class FlickrSearchService extends Service {
	// Message type constants
	public static final int ACTION_SEARCH = 1;
	public static final int ACTION_DOWNLOAD = 2;
	
	/** Reference to application */
	private FlickrSearchApplication app;
	
	/** Handler used to store service requests */ 	
	private MessageHandler handler = new MessageHandler();
	/** Counter used to generate action ids */
	private AtomicLong actionsCounter = new AtomicLong();
	/** List of pending action ids */
	private List<Long> pendingActions = new ArrayList<Long>();

	// --------------------
	// SERVICE FUNTIONALITY
	// --------------------
	
	/**
	 * Performs the actual Flickr Search
	 * @param searchTerm The search term
	 */
	private void search(String searchTerm) {
		CacheUtil.deleteCache();
		String searchURL = Flickr.getSearchURL("23a3cf31f7c28e04a841a2a4a7b1079c",searchTerm, FlickrSearchApplication.MAX_IMAGES);
		InputStream in = null;
		try {
			HttpGet get = new HttpGet(searchURL);
			HttpResponse response = app.getHttpClient().execute(get);
			in = response.getEntity().getContent();
			List<FlickrImage> images = Flickr.parserJSONSearchResult(in);
			app.setImages(images);
		} catch (Exception e) {
			app.searchError(e);
			e.printStackTrace();
		} finally {
			if (in != null) try { in.close(); }	catch (Exception e) {}
		}
	}
	
	/**
	 * Downloads an image
	 * @param info
	 */
	private void downloadImage(DownloadInfo info) {
		String cacheKey = info.getCacheKey();
		// Download Bitmap
		InputStream in = null;
		FileOutputStream out = null;
		try {
			HttpGet get = new HttpGet(info.getURL());
			HttpResponse response = app.getHttpClient().execute(get);
			
			// In
			if (info.isThumbnail)
				in = response.getEntity().getContent();
			else {
				in = new ProgressAwareInputStream(response.getEntity().getContent(),response.getEntity().getContentLength(),info.image);
				((ProgressAwareInputStream)in).setOnProgressListener(app);
			}
			// Out
			File file = CacheUtil.file(cacheKey);
			file.getParentFile().mkdirs();
			out = new FileOutputStream(file,false);
			
			IOUtil.copy(in, out);
			if (!info.isThumbnail)
				info.image.downloaded = true;
			app.bitmapDownloaded(info);

		} catch (Exception e) {
			System.out.println("ERROR IN "+info.image.title+" -- "+info.isThumbnail);
			if (!info.isThumbnail) {
				info.image.error = true;
				info.image.downloaded = false;
			}
			app.bitmapDownloaded(info);
		} finally {
			if (in != null) try { in.close(); } catch (Exception e) {}
			if (out != null) try { out.close(); } catch (Exception e) {}
		}

	}
	
	// --------------------
	// LIFE-CYCLE METHODS
	// --------------------
	
	@Override
	public void onCreate() {
		super.onCreate();
		app = (FlickrSearchApplication) getApplication();
		// Register service handler
		app.registerServiceHandler(handler);
	}
	
	@Override
	public void onDestroy() {
		// Unregister handler
		app.registerServiceHandler(null);
		super.onDestroy();
	}
	
	@Override
	public IBinder onBind(Intent intent) {
		// Local service accessed via message handler
		// no need to do anything here.
		return null;
	}
	
	// ----------------
	// MESSAGE HANDLER
	// ----------------
	
	/**
	 * Handles messages sent over from application by invoking 
	 * service functionality.
	 */
	class MessageHandler extends Handler {
		
		@Override
		public void handleMessage(Message msg) {
			final Object[] params = (Object[]) msg.obj;
			switch (msg.what) {
			case ACTION_SEARCH:
				app.getServiceExecutor().execute(new ServiceWorkerThread(new Runnable() {
					public void run() {
						search((String)params[0]);
					}
				}));
				break;
			case ACTION_DOWNLOAD:
				app.getServiceExecutor().execute(new ServiceWorkerThread(new Runnable() {
					public void run() {
						downloadImage((DownloadInfo)params[0]);
					}
				}));
				break;
			}
		}
		
	}
	
	// ---------------------
	// WORKERS
	// ---------------------
	
	/**
	 * Workers are runnables that perform specific requests.
	 * They are also in charge of stoping the service when 
	 * no pending requests exist.
	 */
	class ServiceWorkerThread implements Runnable {
		private long id;
		private Runnable command;
		
		public ServiceWorkerThread(Runnable command) {
			this.id = actionsCounter.incrementAndGet();
			this.command = command;
			synchronized (pendingActions) {
				pendingActions.add(id);
			}
		}
		
		public void run() {
			try {
				command.run();
			} finally {
				synchronized (handler) {
					pendingActions.remove((Long)id);
					if (pendingActions.size() == 0) {
						FlickrSearchService.this.stopSelf();
					}
				}
			}
		}
	}
	
	
	
}
